package com.w3c.pragmatic.handler;

import com.alibaba.fastjson.JSON;
import com.w3c.pragmatic.common.annotation.LogAspect;
import com.w3c.pragmatic.domain.Response;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 切面日志持久化
 *
 * @author wangdong
 * @since 2022/2/9 9:33
 */
@Slf4j
@Aspect
@Component
public class LogAspectHandler {

  // 多参数分隔符
  private static final String SPLIT_CHARACTER = "#";

  /** 切入点拦截规则，拦截配置了LogAspect注解的方法 */
  @Pointcut(value = "@annotation(com.w3c.pragmatic.common.annotation.LogAspect)")
  public void cutService() {}

  /**
   * 执行切面业务（日志存储）
   *
   * @param point 切入点
   * @return Object
   */
  @Around("cutService()")
  public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
    // 先执行业务
    Object result = point.proceed();
    try {
      // 如果响应失败不做切面持久化
      if (ObjectUtils.isNotEmpty(result)
          && result instanceof Response
          && ((Response<?>) result).isSuccess()) {
        // 拦截到切入点，执行业务
        this.handle(point);
      }
    } catch (Exception e) {
      e.printStackTrace();
      log.error("日志记录出错!", e);
    }
    return result;
  }

  /**
   * 分析切入点数据并根据配置执行操作
   *
   * @param joinPoint 切入点对象
   */
  @SneakyThrows
  private void handle(ProceedingJoinPoint joinPoint) {
    // 验证注解（只能配置在方法上）
    Signature signature = joinPoint.getSignature();
    if (!(signature instanceof MethodSignature)) {
      throw new IllegalArgumentException("该注解只能用于方法");
    }
    // 获取拦截的request对象
    ServletRequestAttributes requestAttributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    assert requestAttributes != null;
    HttpServletRequest request = requestAttributes.getRequest();
    // url 例如：/user/info
    String requestURI = request.getRequestURI();
    // 请求方式 例如：POST、GET
    String requestMethodType = request.getMethod();
    // 请求全类名 例如：packageName.className
    String className = joinPoint.getSignature().getDeclaringTypeName();
    // 请求方法名 例如：queryUserDetail
    String methodName = joinPoint.getSignature().getName();
    // 请求参数（转成json,多个参数用分隔符#分隔）
    String reqParams = this.getReqParams(joinPoint.getArgs());
    // 通过反射获取Method对象（需要请求参数对象）
    LogAspect logAspectProperties = this.getLogRecordProperties(joinPoint.getTarget(), signature);
    String desc = logAspectProperties.desc();
    String operateType = logAspectProperties.operateType().getOprType();
    // 获取token解析用户信息
    // String oprUser = this.getOprUser(request);
    // 获取数据唯一键
    // String recordKey = this.getKeyByResult(result);
    // 打印相关信息
    log.info(
        "请求uri={}, 请求类型={}, 类名={}, 方法名称={}, 操作类型={}, 操作描述={}, 参数={}",
        requestURI,
        requestMethodType,
        className,
        methodName,
        operateType,
        desc,
        reqParams);
  }

  /**
   * 根据方法名获取当前方法对应的LogRecord注解对象
   *
   * @param target class对象
   * @param signature 方法名称
   * @return LogRecord注解对象（用于获取注解属性）
   */
  private LogAspect getLogRecordProperties(Object target, Signature signature)
      throws NoSuchMethodException {
    // 通过反射获取被调用方法的Method对象
    Method currentMethod =
        target
            .getClass()
            .getMethod(signature.getName(), ((MethodSignature) signature).getParameterTypes());
    // 获取方法对应的注解
    return currentMethod.getAnnotation(LogAspect.class);
  }

  /**
   * 组装参数列表
   *
   * @param reqArgs 请求参数列表
   * @return 参数str
   */
  private String getReqParams(Object[] reqArgs) {
    // 无参数
    if (reqArgs.length == 0) {
      return "";
    }
    // 存在参数，需要循环处理（多个参数需要用分隔符分隔）
    StringBuilder stringBuilder = new StringBuilder();
    for (Object arg : reqArgs) {
      // stringBuilder.append(arg).append(SPLIT_CHARACTER);
      stringBuilder.append(JSON.toJSONString(arg)).append("#"); // 将参数转成json
    }
    return stringBuilder.substring(0, stringBuilder.lastIndexOf(SPLIT_CHARACTER));
  }
}
